import java.io.*;
import java.util.*;

/**
 * Main class for the entire system, run from the command
 * line.
 * 
 * @author James Stanier
 *
 */
public class Javalin {

	static ArrayList<File> inputFiles;
	static String output_dir;
	static String report_name;

	static String title;
	static String author;

	static int fontsize = 10;

	static String paper = "a4paper";
	static String doctype = "article";
	static String vardisplay = "none";

	static boolean toc = false;
	static boolean in_depth = false;
	static boolean keyword_index = false;
	static boolean code_index = false;
	static boolean twoside = false;

	static String latexPackages;

	static ArrayList<String> finalOutput;
	static ArrayList<String> inDepthSections;
	static ArrayList<ArrayList<String>> codeIndexes;

	/**
	 * Main method.
	 * @param args Input option fields.
	 */
	public static void main(String[] args) {

		inputFiles = new ArrayList<File>();

		// Print help when no arguments are passed.
		if(args.length == 0) {
			System.out.println("");
			System.out.println("************************************************\n" +
					"* JavaLiN Version 1.0 - Java Literate Notation *\n" +
					"************************************************\n" +
					"-------------Written by James Stanier-----------\n\n" +
					"Usage:\n\t Javalin inputfiles outputdir outputfilename\n\n\tOR\n\n" +
					"\t Javalin gui\n\n" +
					"\"option-settings\" is a sequence of settings " +
					"separated by \nspaces, which can be" +
					"written in the following forms:\n\n" +
					"\t optionname=value (e.g. fontsize=10)\n" +
					"\t optionname (e.g. code_index)\n\n" +
					"Option settings are case sensitive. Always use " +
					"lowercase \nwhen entering options. " +
					"Options can either be boolean valued,\ninteger " +
					"valued, or string valued. Mandatory fields " +
					"are \ndenoted with a * character.\n\n" +
					"The integer valued options are:\n\n" +
					"\t fontsize (default: 10)\n\n" +
					"The boolean valued options are:\n\n" +
					"\t toc (default false)\n" +
					"\t in_depth (default false)\n" +
					"\t keyword_index (default false)\n" +
					"\t code_index (default false)\n" +
					"\t twoside (default false)\n\n" +
					"The string valued options are:\n\n" +
					"\t * input_file\n" +
					"\t * output_dir\n" +
					"\t * report_name\n" +
					"\t paper (default a4paper)\n" +
					"\t doctype (default article)\n" +
					"\t variable_display (default none)\n\n" +
					"If you are at all unsure of the meaning of these " +
					"\nsettings, please consult the " +
					"documentation. Upon \ncorrect entry of options, you " +
					"will be prompted for \nthe title" +
					" of the generated report and the name of \nthe author.\n\n" +
					"Example:\n" +
					"\tJavalin input_file=/Users/js/Desktop/files/" +
					"myclass.java\n\toutput_dir=" +
					"/Users/js/Desktop/output\n\treport_name=" +
					"report \n\tin_depth code_index variable_display=" +
					"grid fontsize=11\n\n"
			);
		}
		else {
			// GUI launching code
			if(args.length == 1 && args[0].equals("gui")) {
				GUI.main(new String[0]);
			}
			else {
				// Add input files to array.
				int argcount = 0;
				if(args[argcount].startsWith("input_file=")) {

					while(true) {
						if(argcount == args.length) {
							break;
						}
						if(!args[argcount].startsWith("input_file=")) {
							break;
						}
						
						File f = new File(args[argcount].substring(11));
						if(f.exists()) {
							inputFiles.add(f);
							argcount++;
						}
						else {
							System.out.println("** Error: specified file at argument "
									+ argcount + " does not exist.");
							System.exit(1);
						}
					}

					// Check another command exists..
					if(argcount == args.length) {
						System.out.println("** Error: output directory must follow " +
						"after input file arguments.");
						System.exit(1);
					}

					// Get output directory
					if(args[argcount].startsWith("output_dir=")) {
						output_dir = args[argcount].substring(11);
						argcount++;
					}
					else {
						System.out.println("** Error: output directory must follow " +
						"after input file arguments.");
						System.exit(1);
					}

					// Check another command exists..
					if(argcount == args.length) {
						System.out.println("** Error: report name must follow " +
						"after output directory argument.");
						System.exit(1);
					}

					// Get report name
					if(args[argcount].startsWith("report_name=")) {
						report_name = args[argcount].substring(12);
						argcount++;
					}
					else {
						System.out.println("** Error: report name must follow " +
						"after output directory argument.");
						System.exit(1);
					}

					// All arguments from here on are optional
					if(argcount == args.length) {
						System.out.println("No further arguments");
						System.exit(1);
					}
					else {
						// Loop for picking up optional arguments
						while(argcount != args.length) {

							if(args[argcount].equals("toc")) {
								toc = true;
								argcount++;
							}
							else if(args[argcount].equals("in_depth")) {
								in_depth = true;
								argcount++;
							}
							else if(args[argcount].equals("code_index")) {
								code_index = true;
								argcount++;
							}
							else if(args[argcount].equals("keyword_index")) {
								keyword_index = true;
								argcount++;
							}
							else if(args[argcount].equals("twoside")) {
								twoside = true;
								argcount++;
							}
							else if(args[argcount].startsWith("fontsize=")) {
								int temp = Integer.parseInt(args[argcount].substring(9));
								if(temp > 9 && temp < 50) {
									fontsize = temp;
									argcount++;
								}
								else {
									System.out.println("** Error: invalid font " +
									"size specified.");
									System.exit(1);
								}
							}
							else if(args[argcount].startsWith("paper=")) {
								ArrayList<String> paperTypes = new ArrayList<String>();
								paperTypes.add("a4paper");
								paperTypes.add("a5paper");
								paperTypes.add("b5paper");
								paperTypes.add("legalpaper");
								paperTypes.add("letterpaper");

								String temp = args[argcount].substring(6);

								if(paperTypes.contains(temp)) {
									paper = temp;
									argcount++;
								}
								else {
									System.out.println("** Error: unknown paper type specified.");
									System.exit(1);
								}
							}
							else if(args[argcount].startsWith("doctype=")) {
								ArrayList<String> docTypes = new ArrayList<String>();
								docTypes.add("article");
								docTypes.add("report");
								docTypes.add("letter");
								docTypes.add("book");
								docTypes.add("slides");

								String temp = args[argcount].substring(8);

								if(docTypes.contains(temp)) {
									doctype = temp;
									argcount++;
								}
								else {
									System.out.println("** Error: unknown document type specified.");
									System.exit(1);
								}
							}
							else if(args[argcount].startsWith("variable_display=")) {
								String temp = args[argcount].substring(17);

								if(temp.equals("grid") || temp.equals("index")) {
									vardisplay = temp;
									argcount++;
								}
								else {
									System.out.println("** Error: unknown variable display type specified.");
									System.exit(1);
								}
							}
							else {
								// Catch anything else
								System.out.println("** Error: unknown argument (" + args[argcount] + ")");
								System.exit(1);
							}
						}
					}

					// Prompt for title and author
					System.out.print("%% Please enter a title for the report: ");
					BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

					try {
						title = br.readLine();

						System.out.print("%% Please enter your name: ");
						author = br.readLine();
					}
					catch (Exception e) {
						System.out.println("** Error: I/O error reading report title.");
						System.exit(1);
					}

					// Now create report.
					createReport();

					System.exit(1);
				}
				else {
					System.out.println("** Error: input files must be specified first in the arguments.");
					System.exit(1);
				}
			}
		}
	}

	/**
	 * Create the report from the input options.
	 */
	public static void createReport() {

		inDepthSections = new ArrayList<String>();
		codeIndexes = new ArrayList<ArrayList<String>>();
		finalOutput = new ArrayList<String>();

		latexPackages = "\\usepackage{geometry}\n" +
		"\\usepackage{amssymb}\n" +
		"\\usepackage{epstopdf}\n" +
		"\\usepackage{longtable}\n" +
		"\\usepackage[usenames]{color}\n" +
		"\\usepackage[parfill]{parskip}\n";

		String temp = "";
		if(twoside) {
			temp = ",twoside";
		}

		finalOutput.add("\\documentclass[" + fontsize + "pt" + temp + "]{" + 
				doctype + "}\n");
		finalOutput.add(latexPackages);
		finalOutput.add("\\geometry{" + paper + "}\n");


		finalOutput.add("\\begin{titlepage}\n");
		finalOutput.add("\\title{\\Huge\\textbf{" + title + "}}\n");
		finalOutput.add("\\author{" + author + "}\n");
		finalOutput.add("\\end{titlepage}\n");

		finalOutput.add("\\begin{document}\n");
		finalOutput.add("\\maketitle\n");

		if(toc) {
			finalOutput.add("\\tableofcontents\n");
		}

		try {
			finalOutput.add("\\newpage\n");
			
			System.out.println("\n** Javalin is starting...");
			System.out.println("** Processing...");

			for(int i = 0; i < inputFiles.size(); i++) {

				File f = inputFiles.get(i);
				String[] s = new String[1];
				s[0] = f.getAbsolutePath();

				// Parsing the source file
				try {
					JavaParser.main(s);
				}
				catch (Exception e) {
					System.out.println("** Error: " + f.getName() + " failed parsing. " +
							"Make sure all input files are valid Java files" +
							" and can be compiled correctly with javac.");
					System.exit(1);
				}

				// Traversing for in-depth comments
				Node tree = JavaParser.getTree();
				tree.traverseForInDepthComments(tree);
				ArrayList<CommentToken> inDepthComments = JavaParser.getInDepthComments();
				Collections.sort(inDepthComments);

				String varListOpt = "none";
				boolean isVarOutput = false;
				// Generating in-depth content and keywords
				if(vardisplay.equals("index")) {
					isVarOutput = true;
					varListOpt = "index";
				}
				else if(vardisplay.equals("grid")) {
					isVarOutput = true;
					varListOpt = "grid";
				}

				InDepthToLaTeX id2l = new InDepthToLaTeX(inDepthComments, JavaParser.getFileName(),
						JavaParser.createVariableOutput(varListOpt));
				try {
					id2l.prepareOutput();
				}
				catch(Exception e) {
					if(e.getMessage().equals("1")) {
						System.out.println("** Error: The Java file " + f.getName() + " contained " +
								"a section heading without a closing * character. " +
						"Please correct this, then try again.\n");
						System.exit(1);
					}
					else if(e.getMessage().equals("2")) {
						System.out.println("** Error:The Java file " + f.getName() + " contained " +
								"a math-mode mark-up without a closing $ character. " +
						"Please correct this, then try again.\n");
						System.exit(1);
					}
					else if(e.getMessage().equals("3")) {
						System.out.println("** Error: The Java file " + f.getName() + " contained " +
								"a keyword-mode mark-up without a closing | character. " +
						"Please correct this, then try again.\n");
						System.exit(1);
					}
					else {
						System.out.println("** Error: The Java file " + f.getName() + " contained " +
								"an invalid comment. All !open comments must be paired with a matching !shut. " +
						"Please correct this, then try again.\n");
						System.exit(1);
					}
				}
				ArrayList<LaTeXCommentToken> transOut = id2l.getTranslatedOutput();


				// Create in-depth section
				InDepthSectionCreator idsc = new InDepthSectionCreator(JavaParser.getFileName(), transOut);
				try {
					idsc.insertCode();
				}
				catch(Exception e) {
					System.out.println("** Error: !shut comment found before !open comment in " + 
							f.getName() + ". Please check that all Javalin comments are in " +
									"the correct order.");
				}

				if(!varListOpt.equals("none")) {
					idsc.addVariableIndex(JavaParser.createVariableOutput(varListOpt));
				}

				// Add to storage
				if(in_depth && keyword_index) {
					addInDepthBundle(idsc.getOutput(), id2l.getTranslatedKeywords());
				}
				else if(in_depth && !keyword_index) {
					addInDepthBundle(idsc.getOutput());
				}
				else if(!in_depth && keyword_index) {
					addInDepthBundle(id2l.getTranslatedKeywords());
				}

				// Code index
				if(code_index) {
					CodeIndexGenerator cig = new CodeIndexGenerator(JavaParser.getFileName(), 
							new GridOutput(JavaParser.getSymbolTable(),
									JavaParser.getFileName()).generateStringArray());
					addCodeIndex(cig.generateIndex());
				}
			}

			// Add in-depth sections to final array
			for(int i = 0; i < inDepthSections.size(); i++) {
				finalOutput.add(inDepthSections.get(i));
			}

			for(int i = 0; i < codeIndexes.size(); i++) {
				ArrayList<String> x = codeIndexes.get(i);
				for(int j = 0; j < x.size(); j++) {
					finalOutput.add(x.get(j));
				}
			}

			finalOutput.add("\n\\end{document}\n");
			
			System.out.println("** Writing to file...");
			writeToFile();
			System.out.println("** Process completed successfully.\n");
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Write the report to file.
	 */
	public static void writeToFile() {
		try {
			FileWriter fstream = new FileWriter(output_dir + File.separator + 
					report_name + ".tex");
			BufferedWriter out = new BufferedWriter(fstream);
			
			for(int i = 0; i < finalOutput.size(); i++) {
				out.write(finalOutput.get(i));
			}
			
			out.close();
		}
		catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * Bundles together keyword index and variable grid/index.
	 * @param s Keyword index.
	 * @param t Variable index.
	 */
	public static void addInDepthBundle(String s, String t) {
		inDepthSections.add(s);
		inDepthSections.add(t);
	}

	/**
	 * Stores a keyword or variable index/grid.
	 * @param s Keyword or variable index/grid to be added.
	 */
	public static void addInDepthBundle(String s) {
		inDepthSections.add(s);
	}

	/**
	 * Adds a code index to the output.
	 * @param s Code index to be added.
	 */
	public static void addCodeIndex(ArrayList<String> s) {
		codeIndexes.add(s);
	}

}
